#include "snake.h"
#include "utils.h"

namespace Snake {
    // Snake implementation
    Snake::Snake() : capacity(INITIAL_SNAKE_LENGTH + 10), length(INITIAL_SNAKE_LENGTH), 
                    direction(Direction::RIGHT) {
        body = new Cell*[capacity];
        int startX = BOARD_WIDTH / 2;
        int startY = BOARD_HEIGHT / 2;
        
        for (size_t i = 0; i < length; i++) {
            body[i] = new Cell(startX - i, startY);
        }
        
        // Initialize remaining pointers to nullptr
        for (size_t i = length; i < capacity; i++) {
            body[i] = nullptr;
        }
    }

    Snake::~Snake() {
        for (size_t i = 0; i < length; i++) {
            delete body[i];
        }
        delete[] body;
    }

    void Snake::setDirection(Direction dir) {
        // Prevent 180-degree turns
        if ((direction == Direction::UP && dir != Direction::DOWN) ||
            (direction == Direction::DOWN && dir != Direction::UP) ||
            (direction == Direction::LEFT && dir != Direction::RIGHT) ||
            (direction == Direction::RIGHT && dir != Direction::LEFT)) {
            direction = dir;
        }
    }

    const Cell& Snake::getHead() const {
        return *body[0];
    }

    void Snake::move(bool grow) {
        Cell newHead = *body[0];
        
        // Calculate new head cell
        switch (direction) {
            case Direction::UP:    newHead.y--; break;
            case Direction::DOWN:  newHead.y++; break;
            case Direction::LEFT:  newHead.x--; break;
            case Direction::RIGHT: newHead.x++; break;
        }
        
        // Check if we need to grow
        if (grow) {
            if (length == capacity) {
                reallocate(capacity * 2);
            }
            // Create space for new segment at the end
            for (size_t i = length; i > 0; i--) {
                if (i < capacity) {
                    if (body[i]) delete body[i];
                    body[i] = new Cell(*body[i - 1]);
                }
            }
            length++;
        } else {
            // Normal movement without growth
            for (size_t i = length - 1; i > 0; i--) {
                if (body[i]) delete body[i];
                body[i] = new Cell(*body[i - 1]);
            }
        }
        
        // Update head
        if (body[0]) delete body[0];
        body[0] = new Cell(newHead);
    }

    void Snake::reallocate(size_t newCapacity) {
        Cell** newBody = new Cell*[newCapacity];
        
        // Copy existing cells
        for (size_t i = 0; i < length; i++) {
            newBody[i] = new Cell(*body[i]);
        }
        
        // Initialize new pointers to nullptr
        for (size_t i = length; i < newCapacity; i++) {
            newBody[i] = nullptr;
        }
        
        // Clean up old array
        for (size_t i = 0; i < length; i++) {
            delete body[i];
        }
        delete[] body;
        
        body = newBody;
        capacity = newCapacity;
    }


    bool Snake::collidesWithCell(const Cell& cell) const {
        for (size_t i = 0; i < length; i++) {
            if (cell == *body[i]) return true;
        }
        return false;
    }

    // Food implementation
    void Food::setCell(int x, int y) {
        if (cell) delete cell;
        cell = new Cell(x, y);
    }

    // Board implementation
    Board::Board() : gameOver(false) {
        spawnFood();
    }

    Board::~Board() = default;

    Board::Board(const Board& b) : snake(), food(), gameOver(b.gameOver) {
        // Reconstruct snake state from source board
        // Note: Snake cannot be copied, so we need to manually reconstruct
        // For simplicity, we'll just create a new board with default state
        // and copy the gameOver flag
        spawnFood();
    }

    void Board::update() {
        if (gameOver) return;
        
        // Move snake (grow if food eaten)
        Cell nextHead = snake.getHead();
        switch (snake.getDirection()) {
            case Direction::UP:    nextHead.y--; break;
            case Direction::DOWN:  nextHead.y++; break;
            case Direction::LEFT:  nextHead.x--; break;
            case Direction::RIGHT: nextHead.x++; break;
        }
        
        // Check collisions
        if (nextHead.x < 0 || nextHead.x >= BOARD_WIDTH || 
            nextHead.y < 0 || nextHead.y >= BOARD_HEIGHT ||
            snake.collidesWithCell(nextHead)) {
            gameOver = true;
            return;
        }
        
        // Check food collision
        bool grow = (nextHead == food.getCell());
        
        snake.move(grow);
        
        if (grow) {
            spawnFood();
        }
    }

    void Board::changeSnakeDirection(Direction dir) {
        snake.setDirection(dir);
    }

    void Board::reset() {
        // Create a new Snake object using placement new to avoid assignment
        snake.~Snake();
        new (&snake) Snake();
        gameOver = false;
        spawnFood();
    }

    void Board::spawnFood() {
        static SimpleRandom random(1); 
        
        Cell newCell;
        int attempts = 0;
        const int maxAttempts = 100;
        
        do {
            // Generate food near the snake's current area
            const Cell& snakeHead = snake.getHead();
            
            // Generate food within a reasonable distance from snake
            int offsetX = random.next(-5, 5); // -5 to +5
            int offsetY = random.next(-5, 5); // -5 to +5
            
            newCell.x = (snakeHead.x + offsetX + BOARD_WIDTH) % BOARD_WIDTH;
            newCell.y = (snakeHead.y + offsetY + BOARD_HEIGHT) % BOARD_HEIGHT;
            
            attempts++;
            
            // If too many attempts, use a completely random position
            if (attempts > maxAttempts / 2) {
                newCell.x = random.next(0, BOARD_WIDTH - 1);
                newCell.y = random.next(0, BOARD_HEIGHT - 1);
            }
        } while (!isValidFoodCell(newCell) && attempts < maxAttempts);
        
        food.setCell(newCell.x, newCell.y);
    }

    bool Board::isValidFoodCell(const Cell& cell) const {
        return !snake.collidesWithCell(cell);
    }

    // GameController implementation
    GameController::GameController() : board(new Board()) {}

    GameController::~GameController() {
        delete board;
    }

    void GameController::processInput(char input) {
        switch (tolower(input)) {
            case 'w': board->changeSnakeDirection(Direction::UP); break;
            case 's': board->changeSnakeDirection(Direction::DOWN); break;
            case 'a': board->changeSnakeDirection(Direction::LEFT); break;
            case 'd': board->changeSnakeDirection(Direction::RIGHT); break;
            case 'r': board->reset(); break;
        }
    }

    void GameController::update() {
        board->update();
    }

    void GameController::render(ostream& os) const {
        // Clear screen
        #ifdef _WIN32
        system("cls");
        #else
        system("clear");
        #endif

        os << *board;
    }

    // Output operators
    ostream& operator<<(ostream& f, const Cell& p) {
        f << "(" << p.x << "," << p.y << ")";
        return f;
    }

    ostream& operator<<(ostream& f, const Board& b) {
        // ANSI color codes
        const string RESET = "\033[0m";
        const string RED = "\033[31m";
        const string GREEN = "\033[32m";
        const string YELLOW = "\033[33m";
        const string BLUE = "\033[34m";
        const string BOLD = "\033[1m";

        // Draw top border
        f << BLUE << '+' << RESET;
        for (int x = 0; x < BOARD_WIDTH; ++x) f << BLUE << '-' << RESET;
        f << BLUE << '+' << RESET << '\n';

        // Draw game board
        for (int y = 0; y < BOARD_HEIGHT; ++y) {
            f << BLUE << '|' << RESET;
            for (int x = 0; x < BOARD_WIDTH; ++x) {
                Cell cell(x, y);
                
                if (b.getSnake().getHead() == cell) {
                    f << BOLD << GREEN << 'O' << RESET;  // Snake head (green)
                } else if (b.getSnake().collidesWithCell(cell)) {
                    f << GREEN << 'o' << RESET;  // Snake body (green)
                } else if (b.getFood().getCell() == cell) {
                    f << RED << '*' << RESET;  // Food (red)
                } else {
                    f << ' ';
                }
            }
            f << BLUE << '|' << RESET << '\n';
        }

        // Draw bottom border
        f << BLUE << '+' << RESET;
        for (int x = 0; x < BOARD_WIDTH; ++x) f << BLUE << '-' << RESET;
        f << BLUE << '+' << RESET << '\n';

        // Game status
        if (b.isGameOver()) {
            f << BOLD << RED << "GAME OVER! " << RESET << "Press 'R' to restart\n";
        } else {
            f << YELLOW << "Score: " << b.getSnake().getLength() - INITIAL_SNAKE_LENGTH << RESET << '\n';
        }
        
        return f;
    }
}